#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//****动态内存管理****

//int main()
//{
//	//****为什么存在动态内存分配****
//	
//	//我们已经掌握的内存开辟方式有：
//	int val = 20; //在栈上开辟四个字节
//	char arr[10] = { 0 }; //在栈空间上开辟10个字节的连续空间
//
//	//但是上述的开辟空间的方式有两个特点：
//	// 1.空间开辟大小是固定的。
//	//2.数组在申明的时候，必须指定数组的长度，它所需要的内存在编译时分配。
//
//	//但是对于空间的需求，不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道，
//	//那数组的编译时开辟空间的方式就不能满足了。
//	//这时候只能试试动态内存开辟了。
//
//	return 0;
//}



//****动态内存函数的介绍****

	//C语言有这几个动态管理内存的能力
	// malloc
	// free
	// calloc
	// realloc

// 1.malloc 和 free
//C语言提供了一个动态内存开辟的函数：
// void* malloc(size_t size);

// 这个函数向内存申请一块连续可用的空间，并返回指向这块空间的指针。
//  如果开辟成功，贼返回一个指向开辟好空间的指针。
//  如果开辟失败，贼返回一个NULL指针，因此malloc的返回值一定要做检查。
//  返回值的类型是void* ，所以malloc函数并不知道开辟空间的类型，具体在使用的时候使用者自己来决定。
//  如果参数 size 为 0， malloc的行为是标准未定义的，取决于编译器。


//int main()
//{
//	////传统的申请40个字节大小的空间
//	//int arr1[10]; //40个字节的大小
//	//char arr2[40]; //四十个字节
//
//	//申请空间
//	int* ptr =(int *) malloc(40);
//	int* p = ptr;
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//		printf("申请失败");
//	}
//	//使用申请的空间
//	//初始化空间
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*p = i;
//		p++;
//     //*（ptr+i）= i;
//	}
//
//	//****释放空间free****
//	//void* free(void* memblock);
//	//free函数用来释放动态开辟的内存。
//   // 如果参数memblock指向的空间不是动态开辟的，那free函数的行为是未定义的。
//  // 如果参数memblock是NULL指针，则函数什么事都不用做。
//  
// 
//	free(ptr); //要保证是首地址
//	//释放之后指针就要置为空指针
//	ptr = NULL;
//	/*if (ptr != NULL)
//	{
//		*ptr = 100;
//	}*/
//	//*ptr = 100;//err
//	return 0;
// // malloc 和 free 都声明在<stdlib.h>中
//}

//int main()
//{
//	int* p = NULL;
//	free(p); //啥事都不发生
//	return 0;
//}
// 当我们不释放动态申请的内存的时候
//如果程序结束，动态申请的内存由操作系统回收
//但是如果程序不结束，动态内存是不会回收的，就会形成内存泄露问题


//****calloc函数****
//C语言还提供了一个函数叫calloc，它也是用来动态内存分配的。原型如下：
// void* calloc(size_t num,size_t size);
//函数的功能是为num个大小为size的元素开辟的一块空间，并且把空间的每一块字节初始化为0.
//与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0.

//int main()
//{
//	int* p = (int*)calloc(10, sizeof(int)); //calloc函数申请的空间会被初始化为0
//	if (p == NULL)
//	{
//		perror("calloc");
//	}
//	//使用
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//	free(p);
//
//	return 0;
//}

//****realloc函数****
// 重新开辟的意思
//realoc函数的出现让动态内存管理更加灵活
//有时候我们会发现过去申请的空间太小了，有时候我们会觉得申请的空间过大了，那为了合理的时候内存，
//我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到动态开辟内存大小的调整。
//函数原型如下：
// void* realloc (void* ptr,size_t size)
// ptr 是要调整的内存地址
//size调整之后的大小
//返回值为调整之后的内存起始位置。
//这个函数调整原内存空间的大小的基础上，还会将原来内存中的数据移动到新的空间。
//realloc在调整内存空间的是存在两种情况：
//   1.原有空间之后有足够大的空间：
// 
//    要扩展内存就直接在原有内存之后直接追加空间，原来空间的数据不发生变化。
// 
//   2.原有空间之后没有足够大的空间：
//	
//		原有空间之后没有足够多的空间时，扩展的方法是：在堆空间上另找一个合适大小的连续空间来使用。
//      这样函数返回的是一个新的内存地址。
//     
//如果扩容失败也会返回一个空指针

//int main()
//{
//		//申请空间
//	int* p =(int *) malloc(40);
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//		printf("申请失败");
//	}
//	//使用申请的空间
//	//初始化空间
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*p = i;
//		p++;
//     //*（ptr+i）= i;
//	}
//	
//	//这时候感觉空间不够了，要放20的元素，要考虑扩容
//		int *ptr = (int *)realloc(p,80);//80个字节
//		if (ptr != NULL)
//		{
//			p = ptr;
//		}
//		//扩容成功,开始使用
//
//		//不再使用，就释放
//		free(p);
//		p =  NULL;
//	return 0;
//}

//****常见的动态内存错误****


//1.****对NULL指针的解引用操作****
//对malloc的返回值进行判断

//int main()
//{
//	int* p = (int*)malloc(1000);
//	int i = 0;
//	if (p == NULL)
//	{
//		//...
//		return 1;
//	}
//	//使用
//	for (i = 0; i < 250; i++)
//	{
//		*(p + i) = i; //但是这里p有可能是空指针
//		//如何避免呢?
//	}
//	free(p);
//	p = NULL;
//	return 0;
//}

//2.****对动态开辟空间的越界访问****
//和数组相似
//解决：对内存边界要检查！自己来检查！编译器不会！
//int main()
//{
//	int* p = (int*)malloc(100);
//	int i = 0;
//	if (p == NULL)
//	{
//		//...
//		return 1;
//	}
//	//使用
//	for (i = 0; i <= 25; i++) //这里写成 <= 的话就形成了越界访问
//	{
//		*(p + i) = i; 
//	}
//	free(p);
//	p = NULL;
//
//	return 0;
//}

//3.****对非动态开辟内存使用free释放****
//解决：
//int main()
//{
//	int a = 10;
//	int* p = &a;
//	//..
//	free(p);
//	p = NULL;
//	//这种释放了，就会报错.
//	//a是由编译器来释放的
//	return 0;
//}

//4.****使用free释放一块动态开辟内存的一部分****
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*p = i;
//		p++;
//	}
//	//释放空间
//	free(p);
//	p = NULL;
//	//会报错
//	//因为这时候p没有指向起始位置
//	//这就相当于释放了一部分空间
//	//必须从起始位置开始释放
//	return 0;
//}

//5.*****对同一块内存的多次释放****
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	free(p);
//	//...
//	free(p);
//	//这时候也会报错
//	p = NULL;
//	return 0;
//}

//6.****动态开辟内存忘记释放（内存泄漏）****
//void test()
//{
//	int* p = malloc(100);
//	//使用
// // if(1)
// {
//		return 1;
// } 
// 
//	//free(p);
//	// p=NULL;
//}
//int main()
//{
//	test();
//	//...
//	while (1)
//	{
//		//...
//		;
//	}
//	return 0;
//}

//****几个经典的笔试题****

//1.下面这个代码的运行结果是什么?
//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);
//	//内存泄露
//}
////p在传完参执行完之后，p的空间就会被系统释放掉，因为不是全局的，被释放掉之后也就没有东西
////记的malloc开辟空间的地址了，所以下面在执行的时候//
//
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(str);
//	//之后str依然为空
//	strcpy(str, "hello world"); // 这里相当于把hello world放到空指针所指向的地方
//	//对空指针的解引用操作，程序崩溃
// //当str指向的不是空指针的时候，是有效地址的话，那么就会拷贝成功
//	printf(str);
//}
//
//int main()
//{
//	Test();
//	return 0;
//}
//结果是：hello world打印不出来,程序崩溃.


//正确书写的方式
//void GetMemory(char** p)
//{
//	*p = (char*)malloc(100);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str);
//
//	strcpy(str, "hello world");
//	printf(str);
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

//2.下面这个代码运行的结果是什么？

//char* GetMemory(void)
//{
//	char p[] = "hello world";
//	return p;
//}
////这里在执行之后的这块数组内存地址就被销毁了，不属于后面的了
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	//这里传进去的只是一个p的地址
//	//是无法访问里面的内容的
//	//str就是一个野指针
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//结果：乱码，非法访问空间了
//属于，返回栈空间地址的问题!!

//又如：
//int* test()
//{
//	int a = 10;
//	return &a;
//}
//int main()
//{
//	int *p = test();
//	printf("%d\n", *p);
//	printf("%d\n", *p);
//
//	return 0;
//}
//这里面打印出来了，但并不代表这是对的，卡好没有被别人改掉而已
//第二次打印就会报错


//3.下面这个代码运行的结果是什么？

//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	peinrf(str);
//	////没有释放
//	//free(str);
//	//str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}
//结果:是能够打印的，但是没有free
//问题就是没有释放

//4.下面这个代码运行的结果是什么？

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	//到这里都很正常
	free(str);

	//但是释放之后，表示str所指向的呢个空间返回给系统了，无法在被访问了
	//但是str存的还是呢块空间的地址
	//所以再释放之后要把str置为空

	if (str != NULL)
	{
		strcpy(str, "world");
		//这里就是非法访问了
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}
//结果，能够打印但是非法访问内存了

//5. c/c++程序在内存中的开辟
